//
// File.cs: Provides a basic framework for reading from and writing to
// a file, as well as accessing basic tagging and media properties.
//
// Author:
//   Brian Nickel (brian.nickel@gmail.com)
//   Aaron Bockover (abockover@novell.com)
//
// Original Source:
//   tfile.cpp from TagLib
//
// Copyright (C) 2005, 2007 Brian Nickel
// Copyright (C) 2006 Novell, Inc.
// Copyright (C) 2002,2003 Scott Wheeler (Original Implementation)
// 
// This library is free software; you can redistribute it and/or modify
// it  under the terms of the GNU Lesser General Public License version
// 2.1 as published by the Free Software Foundation.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//

using System;
using System.Collections.Generic;
using System.Globalization;

namespace TagLib
{

    /// <summary>
    ///    Specifies the level of intensity to use when reading the media
    ///    properties.
    /// </summary>
    public enum ReadStyle
    {
        /// <summary>
        ///    The media properties will not be read.
        /// </summary>
        None = 0,

        // Fast = 1,

        /// <summary>
        ///    The media properties will be read with average accuracy.
        /// </summary>
        Average = 2,

        // Accurate = 3
    }

    /// <summary>
    ///    This abstract class provides a basic framework for reading from
    ///    and writing to a file, as well as accessing basic tagging and
    ///    media properties.
    /// </summary>
    /// <remarks>
    ///    <para>This class is agnostic to all specific media types. Its
    ///    child classes, on the other hand, support the the intricacies of
    ///    different media and tagging formats. For example, <see
    ///    cref="Mpeg4.File" /> supports the MPEG-4 specificication and
    ///    Apple's tagging format.</para>
    ///    <para>Each file type can be created using its format specific
    ///    constructors, ie. <see cref="Mpeg4.File(string)" />, but the
    ///    preferred method is to use <see
    ///    cref="File.Create(string,string,ReadStyle)" /> or one of its
    ///    variants, as it automatically detects the appropriate class from
    ///    the file extension or provided mime-type.</para>
    /// </remarks>
    public abstract class File : IDisposable
    {
        #region Enums

        /// <summary>
        ///   Specifies the type of file access operations currently
        ///   permitted on an instance of <see cref="File" />.
        /// </summary>
        public enum AccessMode
        {
            /// <summary>
            ///    Read operations can be performed.
            /// </summary>
            Read,

            /// <summary>
            ///    Read and write operations can be performed.
            /// </summary>
            Write,

            /// <summary>
            ///    The file is closed for both read and write
            ///    operations.
            /// </summary>
            Closed
        }

        #endregion



        #region Delegates

        /// <summary>
        ///    This delegate is used for intervening in <see
        ///    cref="File.Create(string)" /> by resolving the file type
        ///    before any standard resolution operations.
        /// </summary>
        /// <param name="abstraction">
        ///    A <see cref="IFileAbstraction" /> object representing the
        ///    file to be read.
        /// </param>
        /// <param name="mimetype">
        ///    A <see cref="string" /> object containing the mime-type
        ///    of the file.
        /// </param>
        /// <param name="style">
        ///    A <see cref="ReadStyle" /> value specifying how to read
        ///    media properties from the file.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> or <see
        ///    langword="null" /> if the resolver could not match it.
        /// </returns>
        /// <remarks>
        ///    <para>A <see cref="FileTypeResolver" /> is one way of
        ///    altering the behavior of <see cref="File.Create(string)" />
        ///    .</para>
        ///    <para>When <see cref="File.Create(string)" /> is called, the
        ///    registered resolvers are invoked in the reverse order in
        ///    which they were registered. The resolver may then perform
        ///    any operations necessary, including other type-finding
        ///    methods.</para>
        ///    <para>If the resolver returns a new <see cref="File" />,
        ///    it will instantly be returned, by <see
        ///    cref="File.Create(string)" />. If it returns <see 
        ///    langword="null" />, <see cref="File.Create(string)" /> will
        ///    continue to process. If the resolver throws an exception
        ///    it will be uncaught.</para>
        ///    <para>To register a resolver, use <see
        ///    cref="AddFileTypeResolver" />.</para>
        /// </remarks>
        public delegate File FileTypeResolver(IFileAbstraction abstraction,
                                               string mimetype,
                                               ReadStyle style);

        #endregion



        #region Private Properties

        /// <summary>
        ///    Contains the current stream used in reading/writing.
        /// </summary>
        private System.IO.Stream file_stream;

        /// <summary>
        ///    Contains the internal file abstraction.
        /// </summary>
        private IFileAbstraction file_abstraction;

        /// <summary>
        ///    Contains the mime-type of the file as provided by <see
        ///    cref="Create(string)" />.
        /// </summary>
        private string mime_type;

        /// <summary>
        ///    Contains the types of tags in the file on disk.
        /// </summary>
        private TagTypes tags_on_disk = TagTypes.None;

        /// <summary>
        ///    Contains buffer size to use when reading.
        /// </summary>
        private static int buffer_size = 1024;

        /// <summary>
        ///    Contains the file type resolvers to use in <see
        ///    cref="Create(string)" />.
        /// </summary>
        private static List<FileTypeResolver> file_type_resolvers
            = new List<FileTypeResolver>();

        /// <summary>
        ///    Contains position at which the invariant data portion of
        ///    the file begins.
        /// </summary>
        private long invariant_start_position = -1;

        /// <summary>
        ///    Contains position at which the invariant data portion of
        ///    the file ends.
        /// </summary>
        private long invariant_end_position = -1;

        /// <summary>
        ///    The reasons (if any) why this file is marked as corrupt.
        /// </summary>
        private List<string> corruption_reasons = null;

        #endregion



        #region Public Static Properties

        /// <summary>
        ///    The buffer size to use when reading large blocks of data
        ///    in the <see cref="File" /> class.
        /// </summary>
        /// <value>
        ///    A <see cref="uint" /> containing the buffer size to use
        ///    when reading large blocks of data.
        /// </value>
        public static uint BufferSize
        {
            get { return (uint)buffer_size; }
        }

        #endregion


        #region Constructors

        /// <summary>
        ///    Constructs and initializes a new instance of <see
        ///    cref="File" /> for a specified path in the local file
        ///    system.
        /// </summary>
        /// <param name="path">
        ///    A <see cref="string" /> object containing the path of the
        ///    file to use in the new instance.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="path" /> is <see langword="null" />.
        /// </exception>
        protected File(string path)
        {
            if (path == null)
                throw new ArgumentNullException("path");

            file_abstraction = new LocalFileAbstraction(path);
        }

        /// <summary>
        ///    Constructs and initializes a new instance of <see
        ///    cref="File" /> for a specified file abstraction.
        /// </summary>
        /// <param name="abstraction">
        ///    A <see cref="IFileAbstraction" /> object to use when
        ///    reading from and writing to the file.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="abstraction" /> is <see langword="null"
        ///    />.
        /// </exception>
        protected File(IFileAbstraction abstraction)
        {
            if (abstraction == null)
                throw new ArgumentNullException("abstraction");

            file_abstraction = abstraction;
        }

        #endregion



        #region Public Properties

        /// <summary>
        ///    Gets a abstract representation of all tags stored in the
        ///    current instance.
        /// </summary>
        /// <value>
        ///    A <see cref="TagLib.Tag" /> object representing all tags
        ///    stored in the current instance.
        /// </value>
        /// <remarks>
        ///    <para>This property provides generic and general access
        ///    to the most common tagging features of a file. To access
        ///    or add a specific type of tag in the file, use <see
        ///    cref="GetTag(TagLib.TagTypes,bool)" />.</para>
        /// </remarks>
        public abstract Tag Tag { get; }

        /// <summary>
        ///    Gets the media properties of the file represented by the
        ///    current instance.
        /// </summary>
        /// <value>
        ///    A <see cref="TagLib.Properties" /> object containing the
        ///    media properties of the file represented by the current
        ///    instance.
        /// </value>
        public abstract Properties Properties { get; }

        /// <summary>
        ///    Gets the tag types contained in the physical file
        ///    represented by the current instance.
        /// </summary>
        /// <value>
        ///    A bitwise combined <see cref="TagLib.TagTypes" /> value
        ///    containing the tag types stored in the physical file as
        ///    it was read or last saved.
        /// </value>
        public TagTypes TagTypesOnDisk
        {
            get { return tags_on_disk; }
            protected set { tags_on_disk = value; }
        }

        /// <summary>
        ///    Gets the tag types contained in the current instance.
        /// </summary>
        /// <value>
        ///    A bitwise combined <see cref="TagLib.TagTypes" /> value
        ///    containing the tag types stored in the current instance.
        /// </value>
        public TagTypes TagTypes
        {
            get { return Tag != null ? Tag.TagTypes : TagTypes.None; }
        }

        /// <summary>
        ///    Gets the name of the file as stored in its file
        ///    abstraction.
        /// </summary>
        /// <value>
        ///    A <see cref="string" /> object containing the name of the
        ///    file as stored in the <see cref="IFileAbstraction" />
        ///    object used to create it or the path if created with a
        ///    local path.
        /// </value>
        public string Name
        {
            get { return file_abstraction.Name; }
        }

        /// <summary>
        ///    Gets the mime-type of the file as determined by <see
        ///    cref="Create(IFileAbstraction,string,ReadStyle)" /> if
        ///    that method was used to create the current instance.
        /// </summary>
        /// <value>
        ///    A <see cref="string" /> object containing the mime-type
        ///    used to create the file or <see langword="null" /> if <see
        ///    cref="Create(IFileAbstraction,string,ReadStyle)" /> was
        ///    not used to create the current instance.
        /// </value>
        public string MimeType
        {
            get { return mime_type; }
            internal set { mime_type = value; }
        }

        /// <summary>
        ///    Gets the seek position in the internal stream used by the
        ///    current instance.
        /// </summary>
        /// <value>
        ///    A <see cref="long" /> value representing the seek
        ///    position, or 0 if the file is not open for reading.
        /// </value>
        public long Tell
        {
            get
            {
                return (Mode == AccessMode.Closed) ?
                    0 : file_stream.Position;
            }
        }

        /// <summary>
        ///    Gets the length of the file represented by the current
        ///    instance.
        /// </summary>
        /// <value>
        ///    A <see cref="long" /> value representing the size of the
        ///    file, or 0 if the file is not open for reading.
        /// </value>
        public long Length
        {
            get
            {
                return (Mode == AccessMode.Closed) ?
                    0 : file_stream.Length;
            }
        }

        /// <summary>
        ///    Gets the position at which the invariant portion of the
        ///    current instance begins.
        /// </summary>
        /// <value>
        ///    A <see cref="long" /> value representing the seek
        ///    position at which the file's invariant (media) data
        ///    section begins. If the value could not be determined,
        ///    <c>-1</c> is returned.
        /// </value>
        public long InvariantStartPosition
        {
            get { return invariant_start_position; }
            protected set { invariant_start_position = value; }
        }

        /// <summary>
        ///    Gets the position at which the invariant portion of the
        ///    current instance ends.
        /// </summary>
        /// <value>
        ///    A <see cref="long" /> value representing the seek
        ///    position at which the file's invariant (media) data
        ///    section ends. If the value could not be determined,
        ///    <c>-1</c> is returned.
        /// </value>
        public long InvariantEndPosition
        {
            get { return invariant_end_position; }
            protected set { invariant_end_position = value; }
        }

        /// <summary>
        ///    Gets and sets the file access mode in use by the current
        ///    instance.
        /// </summary>
        /// <value>
        ///    A <see cref="AccessMode" /> value describing the features
        ///    of stream currently in use by the current instance.
        /// </value>
        /// <remarks>
        ///    Changing the value will cause the stream currently in use
        ///    to be closed, except when a change is made from <see
        ///    cref="AccessMode.Write" /> to <see cref="AccessMode.Read"
        ///    /> which has no effect.
        /// </remarks>
        public AccessMode Mode
        {
            get
            {
                return (file_stream == null) ?
                    AccessMode.Closed : (file_stream.CanWrite) ?
                   AccessMode.Write : AccessMode.Read;
            }
            set
            {
                if (Mode == value || (Mode == AccessMode.Write
                    && value == AccessMode.Read))
                    return;

                if (file_stream != null)
                    file_abstraction.CloseStream(file_stream);

                file_stream = null;

                if (value == AccessMode.Read)
                    file_stream = file_abstraction.ReadStream;
                else if (value == AccessMode.Write)
                    file_stream = file_abstraction.WriteStream;

                Mode = value;
            }
        }

        /// <summary>
        ///    Indicates if tags can be written back to the current file or not
        /// </summary>
        /// <value>
        ///    A <see cref="bool" /> which is true if tags can be written to the
        ///    current file, otherwise false.
        /// </value>
        public virtual bool Writeable
        {
            get { return !PossiblyCorrupt; }
        }

        /// <summary>
        ///   Indicates whether or not this file may be corrupt.
        /// </summary>
        /// <value>
        /// <c>true</c> if possibly corrupt; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        ///    Files with unknown corruptions should not be written.
        /// </remarks>
        public bool PossiblyCorrupt
        {
            get { return corruption_reasons != null; }
        }

        /// <summary>
        ///   The reasons for which this file is marked as corrupt.
        /// </summary>
        public IEnumerable<string> CorruptionReasons
        {
            get
            {
                return corruption_reasons;
            }
        }

        #endregion



        #region Public Methods

        /// <summary>
        ///	   Mark the file as corrupt.
        /// </summary>
        /// <param name="reason">
        ///    The reason why this file is considered to be corrupt.
        /// </param>
        internal void MarkAsCorrupt(string reason)
        {
            if (corruption_reasons == null)
                corruption_reasons = new List<string>();
            corruption_reasons.Add(reason);
        }

        /// <summary>
        ///    Dispose the current file. Equivalent to setting the
        ///    mode to closed
        /// </summary>
        public void Dispose()
        {
            Mode = AccessMode.Closed;
        }

        /// <summary>
        ///    Saves the changes made in the current instance to the
        ///    file it represents.
        /// </summary>
        public abstract void Save();

        /// <summary>
        ///    Removes a set of tag types from the current instance.
        /// </summary>
        /// <param name="types">
        ///    A bitwise combined <see cref="TagLib.TagTypes" /> value
        ///    containing tag types to be removed from the file.
        /// </param>
        /// <remarks>
        ///    In order to remove all tags from a file, pass <see
        ///    cref="TagTypes.AllTags" /> as <paramref name="types" />.
        /// </remarks>
        public abstract void RemoveTags(TagTypes types);

        /// <summary>
        ///    Gets a tag of a specified type from the current instance,
        ///    optionally creating a new tag if possible.
        /// </summary>
        /// <param name="type">
        ///    A <see cref="TagLib.TagTypes" /> value indicating the
        ///    type of tag to read.
        /// </param>
        /// <param name="create">
        ///    A <see cref="bool" /> value specifying whether or not to
        ///    try and create the tag if one is not found.
        /// </param>
        /// <returns>
        ///    A <see cref="Tag" /> object containing the tag that was
        ///    found in or added to the current instance. If no
        ///    matching tag was found and none was created, <see
        ///    langword="null" /> is returned.
        /// </returns>
        /// <remarks>
        ///    <para>Passing <see langword="true" /> to <paramref
        ///    name="create" /> does not guarantee the tag will be
        ///    created. For example, trying to create an ID3v2 tag on an
        ///    OGG Vorbis file will always fail.</para>
        ///    <para>It is safe to assume that if <see langword="null"
        ///    /> is not returned, the returned tag can be cast to the
        ///    appropriate type.</para>
        /// </remarks>
        /// <example>
        ///    <para>The following example sets the mood of a file to
        ///    several tag types.</para>
        ///    <code lang="C#">string [] SetMoods (TagLib.File file, params string[] moods)
        ///{
        ///   TagLib.Id3v2.Tag id3 = file.GetTag (TagLib.TagTypes.Id3v2, true);
        ///   if (id3 != null)
        ///      id3.SetTextFrame ("TMOO", moods);
        ///   
        ///   TagLib.Asf.Tag asf = file.GetTag (TagLib.TagTypes.Asf, true);
        ///   if (asf != null)
        ///      asf.SetDescriptorStrings (moods, "WM/Mood", "Mood");
        ///   
        ///   TagLib.Ape.Tag ape = file.GetTag (TagLib.TagTypes.Ape);
        ///   if (ape != null)
        ///      ape.SetValue ("MOOD", moods);
        ///      
        ///   // Whatever tag types you want...
        ///}</code>
        /// </example>
        public abstract Tag GetTag(TagTypes type, bool create);

        /// <summary>
        ///    Gets a tag of a specified type from the current instance.
        /// </summary>
        /// <param name="type">
        ///    A <see cref="TagLib.TagTypes" /> value indicating the
        ///    type of tag to read.
        /// </param>
        /// <returns>
        ///    A <see cref="Tag" /> object containing the tag that was
        ///    found in the current instance. If no matching tag
        ///    was found, <see langword="null" /> is returned.
        /// </returns>
        /// <remarks>
        ///    <para>This class merely accesses the tag if it exists.
        ///    <see cref="GetTag(TagTypes,bool)" /> provides the option
        ///    of adding the tag to the current instance if it does not
        ///    exist.</para>
        ///    <para>It is safe to assume that if <see langword="null"
        ///    /> is not returned, the returned tag can be cast to the
        ///    appropriate type.</para>
        /// </remarks>
        /// <example>
        ///    <para>The following example reads the mood of a file from
        ///    several tag types.</para>
        ///    <code lang="C#">static string [] GetMoods (TagLib.File file)
        ///{
        ///   TagLib.Id3v2.Tag id3 = file.GetTag (TagLib.TagTypes.Id3v2);
        ///   if (id3 != null) {
        ///      TextIdentificationFrame f = TextIdentificationFrame.Get (this, "TMOO");
        ///      if (f != null)
        ///         return f.FieldList.ToArray ();
        ///   }
        ///   
        ///   TagLib.Asf.Tag asf = file.GetTag (TagLib.TagTypes.Asf);
        ///   if (asf != null) {
        ///      string [] value = asf.GetDescriptorStrings ("WM/Mood", "Mood");
        ///      if (value.Length &gt; 0)
        ///         return value;
        ///   }
        ///   
        ///   TagLib.Ape.Tag ape = file.GetTag (TagLib.TagTypes.Ape);
        ///   if (ape != null) {
        ///      Item item = ape.GetItem ("MOOD");
        ///      if (item != null)
        ///         return item.ToStringArray ();
        ///   }
        ///      
        ///   // Whatever tag types you want...
        ///   
        ///   return new string [] {};
        ///}</code>
        /// </example>
        public Tag GetTag(TagTypes type)
        {
            return GetTag(type, false);
        }

        /// <summary>
        ///    Reads a specified number of bytes at the current seek
        ///    position from the current instance.
        /// </summary>
        /// <param name="length">
        ///    A <see cref="int" /> value specifying the number of bytes
        ///    to read.
        /// </param>
        /// <returns>
        ///    A <see cref="ByteVector" /> object containing the data
        ///    read from the current instance.
        /// </returns>
        /// <remarks>
        ///    <para>This method reads the block of data at the current
        ///    seek position. To change the seek position, use <see
        ///    cref="Seek(long,System.IO.SeekOrigin)" />.</para>
        /// </remarks>
        /// <exception cref="ArgumentException">
        ///    <paramref name="length" /> is less than zero.
        /// </exception>
        public ByteVector ReadBlock(int length)
        {
            if (length < 0)
                throw new ArgumentException(
                    "Length must be non-negative",
                    "length");

            if (length == 0)
                return new ByteVector();

            Mode = AccessMode.Read;
            byte[] buffer = new byte[length];

            int count = 0, read = 0, needed = length;

            do
            {
                count = file_stream.Read(buffer, read, needed);

                read += count;
                needed -= count;
            } while (needed > 0 && count != 0);

            return new ByteVector(buffer, read);
        }

        /// <summary>
        ///    Writes a block of data to the file represented by the
        ///    current instance at the current seek position.
        /// </summary>
        /// <param name="data">
        ///    A <see cref="ByteVector" /> object containing data to be
        ///    written to the current instance.
        /// </param>
        /// <remarks>
        ///    This will overwrite any existing data at the seek
        ///    position and append new data to the file if writing past
        ///    the current end.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="data" /> is <see langword="null" />.
        /// </exception>
        public void WriteBlock(ByteVector data)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            Mode = AccessMode.Write;

            file_stream.Write(data.Data, 0, data.Count);
        }

        /// <summary>
        ///    Searches forwards through a file for a specified
        ///    pattern, starting at a specified offset.
        /// </summary>
        /// <param name="pattern">
        ///    A <see cref="ByteVector" /> object containing a pattern
        ///    to search for in the current instance.
        /// </param>
        /// <param name="startPosition">
        ///    A <see cref="int" /> value specifying at what
        ///    seek position to start searching.
        /// </param>
        /// <param name="before">
        ///    A <see cref="ByteVector" /> object specifying a pattern
        ///    that the searched for pattern must appear before. If this
        ///    pattern is found first, -1 is returned.
        /// </param>
        /// <returns>
        ///    A <see cref="long" /> value containing the index at which
        ///    the value was found. If not found, -1 is returned.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="pattern" /> is <see langword="null" />.
        /// </exception>
        public long Find(ByteVector pattern, long startPosition,
                          ByteVector before)
        {
            if (pattern == null)
                throw new ArgumentNullException("pattern");

            Mode = AccessMode.Read;

            if (pattern.Count > buffer_size)
                return -1;

            // The position in the file that the current buffer
            // starts at.

            long buffer_offset = startPosition;
            long original_position = file_stream.Position;

            try
            {
                // Start the search at the offset.
                file_stream.Position = startPosition;
                for (var buffer = ReadBlock(buffer_size); buffer.Count > 0; buffer = ReadBlock(buffer_size))
                {
                    var location = buffer.Find(pattern);
                    if (before != null)
                    {
                        var beforeLocation = buffer.Find(before);
                        if (beforeLocation < location)
                            return -1;
                    }

                    if (location >= 0)
                        return buffer_offset + location;

                    // Ensure that we always rewind the stream a little so we never have a partial
                    // match where our data exists between the end of read A and the start of read B.
                    buffer_offset += buffer_size - pattern.Count;
                    if (before != null && before.Count > pattern.Count)
                        buffer_offset -= before.Count - pattern.Count;
                    file_stream.Position = buffer_offset;
                }

                return -1;
            }
            finally
            {
                file_stream.Position = original_position;
            }
        }

        /// <summary>
        ///    Searches forwards through a file for a specified
        ///    pattern, starting at a specified offset.
        /// </summary>
        /// <param name="pattern">
        ///    A <see cref="ByteVector" /> object containing a pattern
        ///    to search for in the current instance.
        /// </param>
        /// <param name="startPosition">
        ///    A <see cref="int" /> value specifying at what
        ///    seek position to start searching.
        /// </param>
        /// <returns>
        ///    A <see cref="long" /> value containing the index at which
        ///    the value was found. If not found, -1 is returned.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="pattern" /> is <see langword="null" />.
        /// </exception>
        public long Find(ByteVector pattern, long startPosition)
        {
            return Find(pattern, startPosition, null);
        }

        /// <summary>
        ///    Searches forwards through a file for a specified
        ///    pattern, starting at the beginning of the file.
        /// </summary>
        /// <param name="pattern">
        ///    A <see cref="ByteVector" /> object containing a pattern
        ///    to search for in the current instance.
        /// </param>
        /// <returns>
        ///    A <see cref="long" /> value containing the index at which
        ///    the value was found. If not found, -1 is returned.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="pattern" /> is <see langword="null" />.
        /// </exception>
        public long Find(ByteVector pattern)
        {
            return Find(pattern, 0);
        }

        /// <summary>
        ///    Searches backwards through a file for a specified
        ///    pattern, starting at a specified offset.
        /// </summary>
        /// <param name="pattern">
        ///    A <see cref="ByteVector" /> object containing a pattern
        ///    to search for in the current instance.
        /// </param>
        /// <param name="startPosition">
        ///    A <see cref="int" /> value specifying at what
        ///    seek position to start searching.
        /// </param>
        /// <param name="after">
        ///    A <see cref="ByteVector" /> object specifying a pattern
        ///    that the searched for pattern must appear after. If this
        ///    pattern is found first, -1 is returned.
        /// </param>
        /// <returns>
        ///    A <see cref="long" /> value containing the index at which
        ///    the value was found. If not found, -1 is returned.
        /// </returns>
        /// <remarks>
        ///    Searching for <paramref name="after" /> is not yet
        ///    implemented.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="pattern" /> is <see langword="null" />.
        /// </exception>
        long RFind(ByteVector pattern, long startPosition,
                    ByteVector after)
        {
            if (pattern == null)
                throw new ArgumentNullException("pattern");

            Mode = AccessMode.Read;

            if (pattern.Count > buffer_size)
                return -1;

            // The position in the file that the current buffer
            // starts at.

            ByteVector buffer;

            // These variables are used to keep track of a partial
            // match that happens at the end of a buffer.

            /*
            int previous_partial_match = -1;
            int after_previous_partial_match = -1;
            */

            // Save the location of the current read pointer.  We
            // will restore the position using Seek() before all 
            // returns.

            long original_position = file_stream.Position;

            // Start the search at the offset.

            long buffer_offset = Length - startPosition;
            int read_size = buffer_size;

            read_size = (int)Math.Min(buffer_offset, buffer_size);
            buffer_offset -= read_size;
            file_stream.Position = buffer_offset;

            // See the notes in find() for an explanation of this
            // algorithm.

            for (buffer = ReadBlock(read_size); buffer.Count > 0;
                buffer = ReadBlock(read_size))
            {

                // TODO: (1) previous partial match

                // (2) pattern contained in current buffer

                long location = buffer.RFind(pattern);
                if (location >= 0)
                {
                    file_stream.Position = original_position;
                    return buffer_offset + location;
                }

                if (after != null && buffer.RFind(after) >= 0)
                {
                    file_stream.Position = original_position;
                    return -1;
                }

                read_size = (int)Math.Min(buffer_offset, buffer_size);
                buffer_offset -= read_size;
                if (read_size + pattern.Count > buffer_size)
                    buffer_offset += pattern.Count;

                file_stream.Position = buffer_offset;
            }

            // Since we hit the end of the file, reset the status
            // before continuing.

            file_stream.Position = original_position;
            return -1;
        }

        /// <summary>
        ///    Searches backwards through a file for a specified
        ///    pattern, starting at a specified offset.
        /// </summary>
        /// <param name="pattern">
        ///    A <see cref="ByteVector" /> object containing a pattern
        ///    to search for in the current instance.
        /// </param>
        /// <param name="startPosition">
        ///    A <see cref="int" /> value specifying at what
        ///    seek position to start searching.
        /// </param>
        /// <returns>
        ///    A <see cref="long" /> value containing the index at which
        ///    the value was found. If not found, -1 is returned.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="pattern" /> is <see langword="null" />.
        /// </exception>
        public long RFind(ByteVector pattern, long startPosition)
        {
            return RFind(pattern, startPosition, null);
        }

        /// <summary>
        ///    Searches backwards through a file for a specified
        ///    pattern, starting at the end of the file.
        /// </summary>
        /// <param name="pattern">
        ///    A <see cref="ByteVector" /> object containing a pattern
        ///    to search for in the current instance.
        /// </param>
        /// <returns>
        ///    A <see cref="long" /> value containing the index at which
        ///    the value was found. If not found, -1 is returned.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="pattern" /> is <see langword="null" />.
        /// </exception>
        public long RFind(ByteVector pattern)
        {
            return RFind(pattern, 0);
        }

        /// <summary>
        ///    Inserts a specifed block of data into the file repesented
        ///    by the current instance at a specified location,
        ///    replacing a specified number of bytes.
        /// </summary>
        /// <param name="data">
        ///    A <see cref="ByteVector" /> object containing the data to
        ///    insert into the file.
        /// </param>
        /// <param name="start">
        ///    A <see cref="long" /> value specifying at which point to
        ///    insert the data.
        /// </param>
        /// <param name="replace">
        ///    A <see cref="long" /> value specifying the number of
        ///    bytes to replace. Typically this is the original size of
        ///    the data block so that a new block will replace the old
        ///    one.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="data" /> is <see langword="null" />.
        /// </exception>
        public void Insert(ByteVector data, long start, long replace)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            Mode = AccessMode.Write;

            if (data.Count == replace)
            {
                file_stream.Position = start;
                WriteBlock(data);
                return;
            }
            else if (data.Count < replace)
            {
                file_stream.Position = start;
                WriteBlock(data);
                RemoveBlock(start + data.Count,
                    replace - data.Count);
                return;
            }

            // Woohoo!  Faster (about 20%) than id3lib at last. I
            // had to get hardcore and avoid TagLib's high level API
            // for rendering just copying parts of the file that
            // don't contain tag data.
            //
            // Now I'll explain the steps in this ugliness:

            // First, make sure that we're working with a buffer
            // that is longer than the *differnce* in the tag sizes.
            // We want to avoid overwriting parts that aren't yet in
            // memory, so this is necessary.

            int buffer_length = buffer_size;

            while (data.Count - replace > buffer_length)
                buffer_length += (int)BufferSize;

            // Set where to start the reading and writing.

            long read_position = start + replace;
            long write_position = start;

            byte[] buffer;
            byte[] about_to_overwrite;

            // This is basically a special case of the loop below.  
            // Here we're just doing the same steps as below, but 
            // since we aren't using the same buffer size -- instead
            // we're using the tag size -- this has to be handled as
            // a special case.  We're also using File::writeBlock()
            // just for the tag. That's a bit slower than using char
            // *'s so, we're only doing it here.

            file_stream.Position = read_position;
            about_to_overwrite = ReadBlock(buffer_length).Data;
            read_position += buffer_length;

            file_stream.Position = write_position;
            WriteBlock(data);
            write_position += data.Count;

            buffer = new byte[about_to_overwrite.Length];
            System.Array.Copy(about_to_overwrite, 0, buffer, 0,
                about_to_overwrite.Length);

            // Ok, here's the main loop.  We want to loop until the
            // read fails, which means that we hit the end of the 
            // file.

            while (buffer_length != 0)
            {
                // Seek to the current read position and read
                // the data that we're about to overwrite. 
                // Appropriately increment the readPosition.

                file_stream.Position = read_position;
                int bytes_read = file_stream.Read(
                    about_to_overwrite, 0, buffer_length <
                    about_to_overwrite.Length ?
                        buffer_length :
                        about_to_overwrite.Length);
                read_position += buffer_length;

                // Seek to the write position and write our
                // buffer. Increment the writePosition.

                file_stream.Position = write_position;
                file_stream.Write(buffer, 0,
                    buffer_length < buffer.Length ?
                        buffer_length : buffer.Length);
                write_position += buffer_length;

                // Make the current buffer the data that we read
                // in the beginning.

                System.Array.Copy(about_to_overwrite, 0,
                    buffer, 0, bytes_read);

                // Again, we need this for the last write.  We
                // don't want to write garbage at the end of our
                // file, so we need to set the buffer size to
                // the amount that we actually read.

                buffer_length = bytes_read;
            }
        }

        /// <summary>
        ///    Inserts a specifed block of data into the file repesented
        ///    by the current instance at a specified location.
        /// </summary>
        /// <param name="data">
        ///    A <see cref="ByteVector" /> object containing the data to
        ///    insert into the file.
        /// </param>
        /// <param name="start">
        ///    A <see cref="long" /> value specifying at which point to
        ///    insert the data.
        /// </param>
        /// <remarks>
        ///    This method inserts a new block of data into the file. To
        ///    replace an existing block, ie. replacing an existing
        ///    tag with a new one of different size, use <see
        ///    cref="Insert(ByteVector,long,long)" />.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        ///    <paramref name="data" /> is <see langword="null" />.
        /// </exception>
        public void Insert(ByteVector data, long start)
        {
            Insert(data, start, 0);
        }

        /// <summary>
        ///    Removes a specified block of data from the file
        ///    represented by the current instance.
        /// </summary>
        /// <param name="start">
        ///    A <see cref="long" /> value specifying at which point to
        ///    remove data.
        /// </param>
        /// <param name="length">
        ///    A <see cref="long" /> value specifying the number of
        ///    bytes to remove.
        /// </param>
        public void RemoveBlock(long start, long length)
        {
            if (length <= 0)
                return;

            Mode = AccessMode.Write;

            int buffer_length = buffer_size;

            long read_position = start + length;
            long write_position = start;

            ByteVector buffer = (byte)1;

            while (buffer.Count != 0)
            {
                file_stream.Position = read_position;
                buffer = ReadBlock(buffer_length);
                read_position += buffer.Count;

                file_stream.Position = write_position;
                WriteBlock(buffer);
                write_position += buffer.Count;
            }

            Truncate(write_position);
        }

        /// <summary>
        ///    Seeks the read/write pointer to a specified offset in the
        ///    current instance, relative to a specified origin.
        /// </summary>
        /// <param name="offset">
        ///    A <see cref="long" /> value indicating the byte offset to
        ///    seek to.
        /// </param>
        /// <param name="origin">
        ///    A <see cref="System.IO.SeekOrigin" /> value specifying an
        ///    origin to seek from.
        /// </param>
        public void Seek(long offset, System.IO.SeekOrigin origin)
        {
            if (Mode != AccessMode.Closed)
                file_stream.Seek(offset, origin);
        }

        /// <summary>
        ///    Seeks the read/write pointer to a specified offset in the
        ///    current instance, relative to the beginning of the file.
        /// </summary>
        /// <param name="offset">
        ///    A <see cref="long" /> value indicating the byte offset to
        ///    seek to.
        /// </param>
        public void Seek(long offset)
        {
            Seek(offset, System.IO.SeekOrigin.Begin);
        }

        #endregion



        #region Public Static Methods

        /// <summary>
        ///    Creates a new instance of a <see cref="File" /> subclass
        ///    for a specified path, guessing the mime-type from the
        ///    file's extension and using the average read style.
        /// </summary>
        /// <param name="path">
        ///    A <see cref="string" /> object specifying the file to
        ///    read from and write to.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> as read from the
        ///    specified path.
        /// </returns>
        /// <exception cref="CorruptFileException">
        ///    The file could not be read due to corruption.
        /// </exception>
        /// <exception cref="UnsupportedFormatException">
        ///    The file could not be read because the mime-type could
        ///    not be resolved or the library does not support an
        ///    internal feature of the file crucial to its reading.
        /// </exception>
        public static File Create(string path)
        {
            return Create(path, null, ReadStyle.Average);
        }

        /// <summary>
        ///    Creates a new instance of a <see cref="File" /> subclass
        ///    for a specified file abstraction, guessing the mime-type
        ///    from the file's extension and using the average read
        ///    style.
        /// </summary>
        /// <param name="abstraction">
        ///    A <see cref="IFileAbstraction" /> object to use when
        ///    reading to and writing from the current instance.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> as read from the
        ///    specified abstraction.
        /// </returns>
        /// <exception cref="CorruptFileException">
        ///    The file could not be read due to corruption.
        /// </exception>
        /// <exception cref="UnsupportedFormatException">
        ///    The file could not be read because the mime-type could
        ///    not be resolved or the library does not support an
        ///    internal feature of the file crucial to its reading.
        /// </exception>
        public static File Create(IFileAbstraction abstraction)
        {
            return Create(abstraction, null, ReadStyle.Average);
        }

        /// <summary>
        ///    Creates a new instance of a <see cref="File" /> subclass
        ///    for a specified path and read style, guessing the
        ///    mime-type from the file's extension.
        /// </summary>
        /// <param name="path">
        ///    A <see cref="string" /> object specifying the file to
        ///    read from and write to.
        /// </param>
        /// <param name="propertiesStyle">
        ///    A <see cref="ReadStyle" /> value specifying the level of
        ///    detail to use when reading the media information from the
        ///    new instance.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> as read from the
        ///    specified path.
        /// </returns>
        /// <exception cref="CorruptFileException">
        ///    The file could not be read due to corruption.
        /// </exception>
        /// <exception cref="UnsupportedFormatException">
        ///    The file could not be read because the mime-type could
        ///    not be resolved or the library does not support an
        ///    internal feature of the file crucial to its reading.
        /// </exception>
        public static File Create(string path,
                                   ReadStyle propertiesStyle)
        {
            return Create(path, null, propertiesStyle);
        }

        /// <summary>
        ///    Creates a new instance of a <see cref="File" /> subclass
        ///    for a specified file abstraction and read style, guessing
        ///    the mime-type from the file's extension.
        /// </summary>
        /// <param name="abstraction">
        ///    A <see cref="IFileAbstraction" /> object to use when
        ///    reading to and writing from the current instance.
        /// </param>
        /// <param name="propertiesStyle">
        ///    A <see cref="ReadStyle" /> value specifying the level of
        ///    detail to use when reading the media information from the
        ///    new instance.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> as read from the
        ///    specified abstraction.
        /// </returns>
        /// <exception cref="CorruptFileException">
        ///    The file could not be read due to corruption.
        /// </exception>
        /// <exception cref="UnsupportedFormatException">
        ///    The file could not be read because the mime-type could
        ///    not be resolved or the library does not support an
        ///    internal feature of the file crucial to its reading.
        /// </exception>
        public static File Create(IFileAbstraction abstraction,
                                   ReadStyle propertiesStyle)
        {
            return Create(abstraction, null, propertiesStyle);
        }

        /// <summary>
        ///    Creates a new instance of a <see cref="File" /> subclass
        ///    for a specified path, mime-type, and read style.
        /// </summary>
        /// <param name="path">
        ///    A <see cref="string" /> object specifying the file to
        ///    read from and write to.
        /// </param>
        /// <param name="mimetype">
        ///    A <see cref="string" /> object containing the mime-type
        ///    to use when selecting the appropriate class to use, or
        ///    <see langword="null" /> if the extension in <paramref
        ///    name="abstraction" /> is to be used.
        /// </param>
        /// <param name="propertiesStyle">
        ///    A <see cref="ReadStyle" /> value specifying the level of
        ///    detail to use when reading the media information from the
        ///    new instance.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> as read from the
        ///    specified path.
        /// </returns>
        /// <exception cref="CorruptFileException">
        ///    The file could not be read due to corruption.
        /// </exception>
        /// <exception cref="UnsupportedFormatException">
        ///    The file could not be read because the mime-type could
        ///    not be resolved or the library does not support an
        ///    internal feature of the file crucial to its reading.
        /// </exception>
        public static File Create(string path, string mimetype,
                                   ReadStyle propertiesStyle)
        {
            return Create(new LocalFileAbstraction(path),
                mimetype, propertiesStyle);
        }

        /// <summary>
        ///    Creates a new instance of a <see cref="File" /> subclass
        ///    for a specified file abstraction, mime-type, and read
        ///    style.
        /// </summary>
        /// <param name="abstraction">
        ///    A <see cref="IFileAbstraction" /> object to use when
        ///    reading to and writing from the current instance.
        /// </param>
        /// <param name="mimetype">
        ///    A <see cref="string" /> object containing the mime-type
        ///    to use when selecting the appropriate class to use, or
        ///    <see langword="null" /> if the extension in <paramref
        ///    name="abstraction" /> is to be used.
        /// </param>
        /// <param name="propertiesStyle">
        ///    A <see cref="ReadStyle" /> value specifying the level of
        ///    detail to use when reading the media information from the
        ///    new instance.
        /// </param>
        /// <returns>
        ///    A new instance of <see cref="File" /> as read from the
        ///    specified abstraction.
        /// </returns>
        /// <exception cref="CorruptFileException">
        ///    The file could not be read due to corruption.
        /// </exception>
        /// <exception cref="UnsupportedFormatException">
        ///    The file could not be read because the mime-type could
        ///    not be resolved or the library does not support an
        ///    internal feature of the file crucial to its reading.
        /// </exception>
        public static File Create(IFileAbstraction abstraction,
                                   string mimetype,
                                   ReadStyle propertiesStyle)
        {
            if (mimetype == null)
            {
                string ext = String.Empty;

                int index = abstraction.Name.LastIndexOf(".") + 1;

                if (index >= 1 && index < abstraction.Name.Length)
                    ext = abstraction.Name.Substring(index,
                        abstraction.Name.Length - index);

                mimetype = "taglib/" + ext.ToLower(
                    CultureInfo.InvariantCulture);
            }

            foreach (FileTypeResolver resolver in file_type_resolvers)
            {
                File file = resolver(abstraction, mimetype,
                    propertiesStyle);

                if (file != null)
                    return file;
            }

            if (!FileTypes.AvailableTypes.ContainsKey(mimetype))
                throw new UnsupportedFormatException(
                    String.Format(
                        CultureInfo.InvariantCulture,
                        "{0} ({1})",
                        abstraction.Name,
                        mimetype));

            Type file_type = FileTypes.AvailableTypes[mimetype];

            try
            {
                File file = (File)Activator.CreateInstance(
                    file_type,
                    new object[] { abstraction, propertiesStyle });

                file.MimeType = mimetype;
                return file;
            }
            catch (System.Reflection.TargetInvocationException e)
            {
                throw e.InnerException;
            }
        }

        /// <summary>
        ///    Adds a <see cref="FileTypeResolver" /> to the <see
        ///    cref="File" /> class. The one added last gets run first.
        /// </summary>
        /// <param name="resolver">
        ///    A <see cref="FileTypeResolver" /> delegate to add to the
        ///    file type recognition stack.
        /// </param>
        /// <remarks>
        ///    A <see cref="FileTypeResolver" /> adds support for 
        ///    recognizing a file type outside of the standard mime-type
        ///    methods.
        /// </remarks>
        public static void AddFileTypeResolver(FileTypeResolver resolver)
        {
            if (resolver != null)
                file_type_resolvers.Insert(0, resolver);
        }

        #endregion



        #region Protected Methods

        /// <summary>
        ///    Resized the current instance to a specified number of
        ///    bytes.
        /// </summary>
        /// <param name="length">
        ///    A <see cref="long" /> value specifying the number of
        ///    bytes to resize the file to.
        /// </param>
        protected void Truncate(long length)
        {
            AccessMode old_mode = Mode;
            Mode = AccessMode.Write;
            file_stream.SetLength(length);
            Mode = old_mode;
        }

        #endregion



        #region Classes

        /// <summary>
        ///    This class implements <see cref="IFileAbstraction" />
        ///    to provide support for accessing the local/standard file
        ///    system.
        /// </summary>
        /// <remarks>
        ///    This class is used as the standard file abstraction
        ///    throughout the library.
        /// </remarks>
        public class LocalFileAbstraction : IFileAbstraction
        {
            /// <summary>
            ///    Contains the name used to open the file.
            /// </summary>
            private string name;

            /// <summary>
            ///    Constructs and initializes a new instance of
            ///    <see cref="LocalFileAbstraction" /> for a
            ///    specified path in the local file system.
            /// </summary>
            /// <param name="path">
            ///    A <see cref="string" /> object containing the
            ///    path of the file to use in the new instance.
            /// </param>
            /// <exception cref="ArgumentNullException">
            ///    <paramref name="path" /> is <see langword="null"
            ///    />.
            /// </exception>
            public LocalFileAbstraction(string path)
            {
                if (path == null)
                    throw new ArgumentNullException("path");

                name = path;
            }

            /// <summary>
            ///    Gets the path of the file represented by the
            ///    current instance.
            /// </summary>
            /// <value>
            ///    A <see cref="string" /> object containing the
            ///    path of the file represented by the current
            ///    instance.
            /// </value>
            public string Name
            {
                get { return name; }
            }

            /// <summary>
            ///    Gets a new readable, seekable stream from the
            ///    file represented by the current instance.
            /// </summary>
            /// <value>
            ///    A new <see cref="System.IO.Stream" /> to be used
            ///    when reading the file represented by the current
            ///    instance.
            /// </value>
            public System.IO.Stream ReadStream
            {
                get
                {
                    return System.IO.File.Open(Name,
                        System.IO.FileMode.Open,
                        System.IO.FileAccess.Read,
                        System.IO.FileShare.Read);
                }
            }

            /// <summary>
            ///    Gets a new writable, seekable stream from the
            ///    file represented by the current instance.
            /// </summary>
            /// <value>
            ///    A new <see cref="System.IO.Stream" /> to be used
            ///    when writing to the file represented by the
            ///    current instance.
            /// </value>
            public System.IO.Stream WriteStream
            {
                get
                {
                    return System.IO.File.Open(Name,
                        System.IO.FileMode.Open,
                        System.IO.FileAccess.ReadWrite);
                }
            }

            /// <summary>
            ///    Closes a stream created by the current instance.
            /// </summary>
            /// <param name="stream">
            ///    A <see cref="System.IO.Stream" /> object
            ///    created by the current instance.
            /// </param>
            public void CloseStream(System.IO.Stream stream)
            {
                if (stream == null)
                    throw new ArgumentNullException("stream");

                stream.Close();
            }
        }

        #endregion



        #region Interfaces

        /// <summary>
        ///    This interface provides abstracted access to a file. It
        //     premits access to non-standard file systems and data
        ///    retrieval methods.
        /// </summary>
        /// <remarks>
        ///    <para>To use a custom abstraction, use <see
        ///    cref="Create(IFileAbstraction)" /> instead of <see
        ///    cref="Create(string)" /> when creating files.</para>
        /// </remarks>
        /// <example>
        ///    <para>The following example uses Gnome VFS to open a file
        ///    and read its title.</para>
        /// <code lang="C#">using TagLib;
        ///using Gnome.Vfs;
        ///
        ///public class ReadTitle
        ///{
        ///   public static void Main (string [] args)
        ///   {
        ///      if (args.Length != 1)
        ///         return;
        ///
        ///      Gnome.Vfs.Vfs.Initialize ();
        ///      
        ///      try {
        ///          TagLib.File file = TagLib.File.Create (
        ///             new VfsFileAbstraction (args [0]));
        ///          System.Console.WriteLine (file.Tag.Title);
        ///      } finally {
        ///         Vfs.Shutdown()
        ///      }
        ///   }
        ///}
        ///
        ///public class VfsFileAbstraction : TagLib.File.IFileAbstraction
        ///{
        ///    private string name;
        ///
        ///    public VfsFileAbstraction (string file)
        ///    {
        ///        name = file;
        ///    }
        ///
        ///    public string Name {
        ///        get { return name; }
        ///    }
        ///
        ///    public System.IO.Stream ReadStream {
        ///        get { return new VfsStream(Name, System.IO.FileMode.Open); }
        ///    }
        ///
        ///    public System.IO.Stream WriteStream {
        ///        get { return new VfsStream(Name, System.IO.FileMode.Open); }
        ///    }
        ///
        ///    public void CloseStream (System.IO.Stream stream)
        ///    {
        ///        stream.Close ();
        ///    }
        ///}</code>
        ///    <code lang="Boo">import TagLib from "taglib-sharp.dll"
        ///import Gnome.Vfs from "gnome-vfs-sharp"
        ///
        ///class VfsFileAbstraction (TagLib.File.IFileAbstraction):
        ///        
        ///        _name as string
        ///        
        ///        def constructor(file as string):
        ///                _name = file
        ///        
        ///        Name:
        ///                get:
        ///                        return _name
        ///                
        ///        ReadStream:
        ///                get:
        ///                        return VfsStream(_name, FileMode.Open)
        ///                
        ///        WriteStream:
        ///                get:
        ///                        return VfsStream(_name, FileMode.Open)
        ///        
        ///if len(argv) == 1:
        ///        Vfs.Initialize()
        ///
        ///        try:
        ///                file as TagLib.File = TagLib.File.Create (VfsFileAbstraction (argv[0]))
        ///                print file.Tag.Title
        ///        ensure:
        ///                Vfs.Shutdown()</code>
        /// </example>
        public interface IFileAbstraction
        {
            /// <summary>
            ///    Gets the name or identifier used by the
            ///    implementation.
            /// </summary>
            /// <value>
            ///    A <see cref="string" /> object containing the 
            ///    name or identifier used by the implementation.
            /// </value>
            /// <remarks>
            ///    This value would typically represent a path or
            ///    URL to be used when identifying the file in the
            ///    file system, but it could be any value
            ///    as appropriate for the implementation.
            /// </remarks>
            string Name { get; }

            /// <summary>
            ///    Gets a readable, seekable stream for the file
            ///    referenced by the current instance.
            /// </summary>
            /// <value>
            ///    A <see cref="System.IO.Stream" /> object to be
            ///    used when reading a file.
            /// </value>
            /// <remarks>
            ///    This property is typically used when creating
            ///    constructing an instance of <see cref="File" />.
            ///    Upon completion of the constructor, <see
            ///    cref="CloseStream" /> will be called to close
            ///    the stream. If the stream is to be reused after
            ///    this point, <see cref="CloseStream" /> should be
            ///    implemented in a way to keep it open.
            /// </remarks>
            System.IO.Stream ReadStream { get; }

            /// <summary>
            ///    Gets a writable, seekable stream for the file
            ///    referenced by the current instance.
            /// </summary>
            /// <value>
            ///    A <see cref="System.IO.Stream" /> object to be
            ///    used when writing to a file.
            /// </value>
            /// <remarks>
            ///    This property is typically used when saving a
            ///    file with <see cref="Save" />. Upon completion of
            ///    the method, <see cref="CloseStream" /> will be
            ///    called to close the stream. If the stream is to
            ///    be reused after this point, <see
            ///    cref="CloseStream" /> should be implemented in a
            ///    way to keep it open.
            /// </remarks>
            System.IO.Stream WriteStream { get; }

            /// <summary>
            ///    Closes a stream originating from the current
            ///    instance.
            /// </summary>
            /// <param name="stream">
            ///    A <see cref="System.IO.Stream" /> object
            ///    originating from the current instance.
            /// </param>
            /// <remarks>
            ///    If the stream is to be used outside of the scope,
            ///    of TagLib#, this method should perform no action.
            ///    For example, a stream that was created outside of
            ///    the current instance, or a stream that will
            ///    subsequently be used to play the file.
            /// </remarks>
            void CloseStream(System.IO.Stream stream);
        }

        #endregion
    }
}
